package br.com.iswe.core.web.webflow;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.util.FilterChainProxy;
import org.springframework.web.context.support.WebApplicationContextUtils;

import br.com.iswe.core.exp.ISWEConfigurationException;
import br.com.iswe.core.exp.ISWEException;
import br.com.iswe.core.exp.ISWEExceptionHandler;
import br.com.iswe.core.exp.ISWESecurityException;

@SuppressWarnings("unchecked")
public class ISWEWebFlowFilter implements Filter {
	
	//Constantes
	private static final String FILTER_DELEGATE = "filterDelegate";
	private static final String ISWE_LOGIN_REQUEST_BEAN_NAME = "ISWE_LOGIN_REQUEST_BEAN_NAME";
	private static final String PROBLEMA_NA_CONFIGURACAO_DO_FILTRO = "Problema na configuracao do Filtro";
	public static final String ISWE_WEB_FLOW_FILTER_ERRO_WEB_FLOW_FILTER_INIT = "iswe.web.flow.filter.erro.web.flow.filter.init";
	public static final String ISWE_WEB_FLOW_FILER_ERRO_BEAN_NAME_NOT_ESPECIED = "iswe.web.flow.filer.erro.bean.name.not.especied";
	public static final String WEB_FLOW_MANAGER = "webFlowManager";
	public static final String ISWE_LISTA_MENSAGENS = "ISWE_LISTA_MENSAGENS";

	//Log class
	private static final Log log = LogFactory.getLog(ISWEWebFlowFilter.class);

	private static HashMap<Class, String> paginasErro = new HashMap<Class, String>();
	
	//Instancia de WebFlowManager
	private ISWEWebFlowManager webFlowManager = null;
	
	//Quando possui um delegate.
	private Filter filterDelegate = null;
	
	public void init(FilterConfig config) throws ServletException {
		//Valor default
		String webFlowManagerBeanName = config.getInitParameter(WEB_FLOW_MANAGER);
		String filterDelegateBeanName = config.getInitParameter(FILTER_DELEGATE);
		try{
			//TODO:Pensar em como customizar essa configuracao.
			paginasErro.put(Exception.class,     "/common/jsp/ISWEException.faces");
			paginasErro.put(ISWEException.class, "/common/jsp/ISWEException.faces");
			paginasErro.put(ISWESecurityException.class, "/common/jsp/Login.jsp");
			
			//** Obt�m a implementacao de WebFlowManager que ser� utilizada **//
			if(webFlowManagerBeanName == null || "".equals(webFlowManagerBeanName)){
				ISWEConfigurationException configExp = new ISWEConfigurationException(ISWE_WEB_FLOW_FILER_ERRO_BEAN_NAME_NOT_ESPECIED);
				throw new ServletException(PROBLEMA_NA_CONFIGURACAO_DO_FILTRO, configExp);
			}
			//Obtem o ApplicationContext
			ApplicationContext ctx = getContext(config);
			webFlowManager = (ISWEWebFlowManager)ctx.getBean(webFlowManagerBeanName);
			//verifica se existe um filterDelegate configurado.
			if(filterDelegateBeanName != null && !"".equals(filterDelegateBeanName)){
				filterDelegate = (FilterChainProxy)ctx.getBean(filterDelegateBeanName);
			}
		}
        catch(NoSuchBeanDefinitionException exp){
            if(log.isErrorEnabled()){
                log.error("N�o foi poss�vel configurar o ISWEWebFlowFilter por n�o existe o bean: " + exp.getBeanName());
            }
            ISWEConfigurationException configExp = new ISWEConfigurationException(ISWE_WEB_FLOW_FILTER_ERRO_WEB_FLOW_FILTER_INIT, exp);
            throw new ServletException(PROBLEMA_NA_CONFIGURACAO_DO_FILTRO, configExp);
        }
		catch(Exception exp){
			ISWEConfigurationException configExp = new ISWEConfigurationException(ISWE_WEB_FLOW_FILTER_ERRO_WEB_FLOW_FILTER_INIT, exp);
			throw new ServletException(PROBLEMA_NA_CONFIGURACAO_DO_FILTRO, configExp);
		}
	}

	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		//Primeiro Passo : Cria um RequestState e armazena no WebFlowManager.
		ISWEHttpRequestWrapper isweRequest = new ISWEHttpRequestWrapper((HttpServletRequest)request);
		
		try{
			ISWERequestState requestState = null;
			//Segundo : Obtem o requestState atual e verifica o seu estado
			requestState = getWebFlowManager().getCurrentRequestState(isweRequest.getSession());
			
			//caso esse request esteja vindo de um erro de seguran�a
			if(requestState != null && requestState.getState() == ISWERequestState.STATE_SECURITY_ERROR){
				//** Caso o request possua um erro de seguran�a
				//Processa a p�gina de decis�o do usu�rio, que pode ser uma tela de login ou uma
				
				//** Verifica se � um requisi��o de Login.
				if(isLoginRequest(isweRequest)){
					doLogin(isweRequest, (HttpServletResponse)response);
					//Altera o estado do requestState
					if(requestState != null){
						requestState.setState(ISWERequestState.STATE_SECURITY_ERROR_HANDLED);
					}
					return;
				}
				
				//simples tela de aviso.
				if(filterDelegate != null){
					//caso possua um filterDelegate
					filterDelegate.doFilter(isweRequest, response, chain);
				}
				else{
					chain.doFilter(isweRequest, response);
				}
			}
			//caso seja um login
			else if(isLoginRequest(isweRequest)){
				//Efetua o login.
				doLogin(isweRequest, (HttpServletResponse)response);
				return;
			}
            //Caso seja um erro de seguran�a ja tratado
			else if(requestState != null && requestState.getState() == ISWERequestState.STATE_SECURITY_ERROR_HANDLED){
				HttpServletRequest requestToPass = isweRequest;
				//** Caso o request possua um erro de seguran�a que ja foi apresentado ao usu�rio
				//verifica se deve restaurar o request do usu�rio
				if(requestState.shouldRestoreRequest()){
					//requestState.restoreRequest(isweRequest);
					requestToPass = new ISWEHttpRestoredRequestWrapper(isweRequest, requestState);
				}
				getWebFlowManager().endCurrentRequest(isweRequest.getSession());
				//Processa o request.
				if(filterDelegate != null){
					//caso possua um filterDelegate
					filterDelegate.doFilter(requestToPass, response, chain);
				}
				else{
					chain.doFilter(requestToPass, response);
				}
			}
			else{
				//** Caso seja um request commun.
				requestState = new ISWERequestState(isweRequest);
				//Armazena o request no WebFlowManager.
				getWebFlowManager().addRequestState(requestState, isweRequest.getSession());
                //Coloca o SecurityContext no SecurityContextHolder
                SecurityContext sessionSecCtx = (SecurityContext)isweRequest.getSession().getAttribute(HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY);
                if(sessionSecCtx != null){
                    SecurityContextHolder.setContext(sessionSecCtx);
                }
				//Processa o request.
				if(filterDelegate != null){
					//caso possua um filterDelegate
					filterDelegate.doFilter(isweRequest, response, chain);
				}
				else{
					chain.doFilter(isweRequest, response);
				}
			}
		}
		catch(ServletException exp){
			String pagina = "";
			//Procura as mensagens das execoes ocorridas
			List<String> mensagens = null;
			//busca se ouve uma exe��o identificada.
			ISWEException isExp = ISWEExceptionHandler.getInstance().findISWEException(exp);
			if(isExp != null){
				//caso tenha uma execao tipada do framework
				//obtem as mensagens traduzidas dos erros ocorridos
				mensagens = ISWEExceptionHandler.getInstance().getMessages(isExp, request.getLocale());
				
				//tenta obter a pagina para a execao especifica
				pagina = getFowardPage(isExp);
				
				//*** verifica se � uma falha de seguran�a ***//
				if(isExp instanceof ISWESecurityException){
					//Primeiro : Obt�m o estado do request.
					ISWERequestState requestState = getWebFlowManager().getCurrentRequestState(isweRequest.getSession());
					//Segundo : Seta no estado do request a exe��o ocorrida
					//requestState.setISWEException(isExp);
					//Terceiro : Informa que o request possui um erro
					if(requestState != null){
						requestState.setState(ISWERequestState.STATE_SECURITY_ERROR);
					}
				}
			}
			else{
				mensagens = new ArrayList<String>();
				mensagens.add("Error n�o identificado ocorrido : " + exp.getMessage());
				//tenta obter a pagina para a execao especifica
				pagina = paginasErro.get(exp.getClass());
				if(pagina == null){
					//caso nao consiga achar.. pega a default
					pagina = paginasErro.get(Exception.class);
				}
			}
			if(log.isErrorEnabled()){
				StringBuffer logMsg = new StringBuffer("Error detectado pelo filtro Web : ");
				for (String msg : mensagens) {
					logMsg.append(msg);
					logMsg.append(", ");
				}
                if(isExp != null){
                    if(isExp.getMoreDetails().size() > 0){
                        logMsg.append("Detalhes da Excecao: \n");
                        for (String msg : isExp.getMoreDetails()) {
                            logMsg.append(msg);
                            logMsg.append("\n");
                        }
                    }
                    log.error(logMsg, isExp);
                    log.error("ServletException: " + exp.getMessage(), exp);
                    if(exp instanceof ServletException){
                        log.error("ServletException RootException: " + exp.getMessage(), ((ServletException)exp).getRootCause() );
                    }
                }
                else{
                    log.error(logMsg, exp);
                    if(exp instanceof ServletException){
                        log.error("ServletException RootException: " + exp.getMessage(), ((ServletException)exp).getRootCause() );
                    }
                }
			}
			//Coloca no request a lista de mensagens.
			((HttpServletRequest)request).getSession().setAttribute(ISWE_LISTA_MENSAGENS, mensagens);
			
			//redireciona para a pagina de erro.
			String url = isweRequest.getContextPath() + pagina;
			((HttpServletResponse)response).sendRedirect(url);
		}
	}

	/**
	 * Efetua o login.
	 * @param isweRequest
	 * @param reponse
	 */
	private void doLogin(HttpServletRequest request, HttpServletResponse response) {
		//Primeir Passo : Obtem o objeto de Login
		String loginBeanName = request.getParameter(ISWE_LOGIN_REQUEST_BEAN_NAME);
		//obtem o aplication context
		ApplicationContext app = getContext(request.getSession().getServletContext());
		ISWELoginHandler loginHandler = (ISWELoginHandler)app.getBean(loginBeanName);
		loginHandler.doLogin((HttpServletRequest)request, response);
	}

	/**
	 * Verifica se o request � um request de login.
	 * @param isweRequest
	 * @return
	 */
	private boolean isLoginRequest(ISWEHttpRequestWrapper isweRequest) {
		return isweRequest.getParameter(ISWE_LOGIN_REQUEST_BEAN_NAME) != null;
	}

	/**
	 * 
	 * @param isExp
	 * @return
	 */
	private String getFowardPage(ISWEException isExp) {
		//TODO:Melhorar essa logica permitindo o usuario configurar melhor esse fluxo.
		String retorno = paginasErro.get(isExp.getClass());
		if(retorno == null){
			retorno = paginasErro.get(ISWEException.class);
		}
		return retorno;
	}

	public void destroy() {
		paginasErro.clear();
	}

	protected ISWEWebFlowManager getWebFlowManager() {
		return webFlowManager;
	}
	protected void setWebFlowManager(ISWEWebFlowManager requestWorkFlowManager) {
		this.webFlowManager = requestWorkFlowManager;
	}
	
	protected ApplicationContext getContext(FilterConfig filterConfig) {
        return getContext(filterConfig.getServletContext());
    }
	protected ApplicationContext getContext(ServletContext servletContext) {
        return WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
    }

	public Filter getFilterDelegate() {
		return filterDelegate;
	}
	public void setFilterDelegate(Filter filterDelegate) {
		this.filterDelegate = filterDelegate;
	}
}
